#include <cassert>

#include "type.hpp"
#include "ast.hpp"

bool IntType::support_operator(OperatorType ot) {
    if (ot == OPERATOR_ADD || ot == OPERATOR_SUB ||
            ot == OPERATOR_MUL || ot == OPERATOR_DIV ||
            ot == OPERATOR_SHIFT) {
        return true;
    }

    return false;
}

bool CharType::support_operator(OperatorType ot) {
    if (ot == OPERATOR_ADD || ot == OPERATOR_SUB) {
        return true;
    }

    return false;
}

bool StringType::support_operator(OperatorType ot) {
    if (ot == OPERATOR_ADD || ot == OPERATOR_MUL) {
        return true;
    }

    return false;
}

ArrowType::~ArrowType() {
}

const char* ArrowType::to_string() {
    return "a -> a";
}

bool ArrowType::equals(Type* t) {
    ArrowType* at = dynamic_cast<ArrowType*>(t);
    if (at == NULL) {
        return false;
    }

    return _src->equals(at->src()) && _dst->equals(at->dst());
}

Type* ArrowType::try_apply(Type* t) {
    if (!_src->equals(t)) {
        return nullptr;
    }
    return _dst;
}

bool ArrayType::equals(Type* t) {
    ArrayType* at = dynamic_cast<ArrayType*>(t);
    if (at == NULL) {
        return false;
    }

    return _elm_type->equals(at->elm_type()) && _size == at->size();
}

UserDefType::~UserDefType() {
}

TypeTable::TypeTable() {
    _type_map["Int"] = IntType::get_instance();
    _type_map["String"] = StringType::get_instance();
    _type_map["Char"] = CharType::get_instance();
    _type_map["Bool"] = BoolType::get_instance();
    _type_map["Unit"] = UnitType::get_instance();
    _type_map["Array"] = ArrayType::make_function();
}

TypeTable::TypeTable(TypeTable* parent) {
    _next = parent;
}

void TypeTable::add_generic_name(Node* n) {
    TypeVarNode* tvn = dynamic_cast<TypeVarNode*>(n);
    assert(tvn != nullptr);

    _generic_names.push_back(tvn->name());
    _type_map[tvn->name()] = new TypeVarType();
}

TypeFunction* ArrayType::make_function() {
    // type Array<T, n> = xxx
    ArrayTypeNode* n = new ArrayTypeNode(new TypeVarNode("T"), new TypeVarNode("n"));
    TypeFunction* tf = new TypeFunction(n);
    tf->args().push_back("T");
    tf->args().push_back("n");
    return tf;
}

Type* TypeTable::get_expanded_type(string& name) {
    TypeTable* tt = this;
    while (tt != nullptr) {
        Type* t = tt->type_map()[name];
        if (t != nullptr)
            return t;
        tt = tt->next();
    }

    return nullptr;
}

